package org.jradweb.core.orm;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Example;
import org.hibernate.id.UUIDHexGenerator;
import org.jradweb.core.utils.StringUtils;

/**
 *  * This class serves as the Base class for all other DAOs - namely to hold
 * common CRUD methods that they might all use. You should only need to extend
 * this class when your require custom CRUD logic.
 * 
 * <p>
 * To register this class in your Spring context file, use the following XML.
 * 
 * <pre>
 *      &lt;bean id=&quot;fooDao&quot; class=&quot;<em>package</em>.GenericHibernateDaoImpl&quot;&gt;
 *          &lt;constructor-arg value=&quot;<em>DomainClassName</em>&quot;/&gt;
 *          &lt;property name=&quot;sessionFactory&quot; ref=&quot;sessionFactory&quot;/&gt;
 *      &lt;/bean&gt;
 * </pre>
 * 
 *
 * Created On 2008-2-28
 * @author <a href="mailto:wagzhi@gmail.com">Paul Wang</a>
 * @version $Id: GenericHibernateDaoImpl.java 149 2008-10-10 03:10:10Z wagzhi $
 * @param <T>  a type variable
 * @param <PK> the primary key for that type
 */
public class GenericHibernateDaoImpl<T, PK extends Serializable> implements
		GenericDao<T, PK> {
	/**
	 * Log variable for all child classes. Uses LogFactory.getLog(getClass())
	 * from Commons Logging
	 */
	protected final Log log = LogFactory.getLog(getClass());
	Class<T> persistentClass;

	private SessionFactory sessionFactory;

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	/**
	 * Constructor that takes in a class to see which type of entity to persist
	 * 
	 * @param persistentClass
	 *            the class type you'd like to persist
	 */
	public GenericHibernateDaoImpl(final Class<T> persistentClass) {
		this.persistentClass = persistentClass;
	}

	public void attachClean(T instance) {
		log.debug("attaching clean Release instance");
		try {
			sessionFactory.getCurrentSession().lock(instance, LockMode.NONE);
			log.debug("attach successful");
		} catch (RuntimeException re) {
			log.error("attach failed", re);
			throw re;
		}
	}

	public void attachDirty(T instance) {
		log.debug("attaching dirty Release instance");
		try {
			sessionFactory.getCurrentSession().saveOrUpdate(instance);
			log.debug("attach successful");
		} catch (RuntimeException re) {
			log.error("attach failed", re);
			throw re;
		}
	}

	public void delete(T persistentInstance) {
		log.debug("deleting Release instance");
		try {
			sessionFactory.getCurrentSession().delete(persistentInstance);
			log.debug("delete successful");
		} catch (RuntimeException re) {
			log.error("delete failed", re);
			throw re;
		}
	}

	public List<T> findByExample(T instance) {
		log.debug("finding Release instance by example");
		try {
			List results = sessionFactory.getCurrentSession().createCriteria(
					this.persistentClass.getName()).add(
					Example.create(instance)).list();
			log.debug("find by example successful, result size: "

			+ results.size());
			return results;
		} catch (RuntimeException re) {
			log.error("find by example failed", re);
			throw re;
		}
	}

	public T findById(PK id) {
		if(log.isDebugEnabled()){
			log.debug("getting instance of \""+persistentClass.getName()+"\" with id: " + id);
		}
		try {
			T instance = (T) sessionFactory.getCurrentSession().get(
					this.persistentClass.getName(), id);
			if (instance == null) {
				if(log.isDebugEnabled()){
					log.debug("get successful, no instance found");
				}
			} else {
				if(log.isDebugEnabled()){
					log.debug("get successful, instance \""+instance+"\" found");
				}
			}
			return instance;
		} catch (RuntimeException re) {
			log.error("get failed", re);
			throw re;
		}
	}

	public List<T> findByQuery(String hql, int firstResult, int maxResult,
			Map<String, Object> parameters) {
		// TODO Auto-generated method stub
		return null;
	}

	public T merge(T detachedInstance) {
		log.debug("merging Release instance");
		try {
			if(AbstractModel.class.isInstance(detachedInstance)){
				AbstractModel model=(AbstractModel)detachedInstance;
				model.setUpdatedAt(new Date());
			}
			T result = (T) sessionFactory.getCurrentSession().merge(
					detachedInstance);
			log.debug("merge successful");
			return result;
		} catch (RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}
	}

	public void persist(T transientInstance) {
		log.debug("persisting Release instance");
		try {
			if(MultiSiteAbstractModel.class.isInstance(transientInstance)){
				MultiSiteAbstractModel model=(MultiSiteAbstractModel)transientInstance;
				if(model.getId().getId()==null){
					UUIDHexGenerator gen=new UUIDHexGenerator();
					model.getId().setId((String)gen.generate(null, null));
				}
			}
			sessionFactory.getCurrentSession().persist(transientInstance);
			log.debug("persist successful");
		} catch (RuntimeException re) {
			log.error("persist failed", re);
			throw re;
		}
	}
	public long getTotalCount(){
		return this.getCountByCondition(null,null);
	}
	
	public long getCountByCondition(String condition,Map<String,Object> params){
		String fullClassName=this.persistentClass.getName();
		String className=StringUtils.getClassName(this.persistentClass);
		StringBuffer queryHql=new StringBuffer("select count(distinct "+className+".id) from ").append(fullClassName)
		.append(" as ").append(className).append(" ");
		if(condition!=null)
		{
			queryHql.append(" ").append(condition);
		}
		log.info(queryHql.toString());
		Query query=sessionFactory.getCurrentSession().createQuery(queryHql.toString());
		
		if(params!=null){
			for(String key:params.keySet()){
				query.setParameter(key, params.get(key));
			}
		}
		Long count=(Long)query.uniqueResult();
		return count==null?0:count;		
	}
	
	public long getCountByHql(String queryHql,Map<String,Object> params){
		Query query=sessionFactory.getCurrentSession().createQuery(queryHql.toString());
		
		if(params!=null){
			for(String key:params.keySet()){
				query.setParameter(key, params.get(key));
			}
		}
		Long count=(Long)query.uniqueResult();
		return count==null?0:count;		
	}
	
	
	public List<T> getAll(){
		return this.getByCondition(null, null);
	}
	
	public List<T> getByCondition(String condition,Map<String,Object> params){
		String fullClassName=this.persistentClass.getName();
		String className=StringUtils.getClassName(this.persistentClass);
		StringBuffer queryHql=new StringBuffer("select ").append(className).append(" from ").append(fullClassName).append(" as ").append(className).append(" ");
		if(condition!=null)
		{
			queryHql.append(" ").append(condition);
		}
		Query query=sessionFactory.getCurrentSession().createQuery(queryHql.toString());

		query.setFirstResult(0);
		query.setMaxResults(10000);
		query.setFetchSize(10000);
		
		if(params!=null){
			for(String key:params.keySet()){
				query.setParameter(key, params.get(key));
			}
		}
		List results=query.list();
		return (List<T>)results;
	}
	public T getUniqueResultByCondition(String condition,Map<String,Object> params){
		
		StringBuffer queryHql=new StringBuffer("from ").append(this.persistentClass.getName());
		if(condition!=null)
		{
			queryHql.append(" ").append(condition);
		}
		Query query=sessionFactory.getCurrentSession().createQuery(queryHql.toString());
		
		if(params!=null){
			for(String key:params.keySet()){
				query.setParameter(key, params.get(key));
			}
		}
		Object result=query.uniqueResult();
		return (T)result;
	}
	
	public List<T> getPage(int pageNumber, int pageSize){
		return this.getPage(pageNumber, pageSize,null,null);
	}

	public List<T> getPage(int pageNumber, int pageSize, String condition,
			Map<String, Object> params) {
		String fullClassName=this.persistentClass.getName();
		String className=StringUtils.getClassName(this.persistentClass);
		try {
			int first = pageSize * (pageNumber - 1);

			if (condition == null)
				condition = "";
			if(params==null){
				params=new HashMap<String,Object>();
			}
			String hql="select "+className+" from " + fullClassName + " as "+ className +" "+ condition;
			log.debug(hql);
			
			Query query = sessionFactory.getCurrentSession().createQuery(hql);
			for (String key : params.keySet()) {
				query.setParameter(key, params.get(key));
			}
			query.setFirstResult(first);
			query.setMaxResults(pageSize);

			log.debug("query successful");
			return (List<T>) query.list();

		} catch (RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}
	}

	public int getPageCount(int pageSize, String condition,
			Map<String, Object> params) {
		String fullClassName=this.persistentClass.getName();
		String className=StringUtils.getClassName(this.persistentClass);
		try {
			if (condition == null)
				condition = "";
			String hql="select count(distinct "+className+") from " +fullClassName
			+ " as "+className+" " + condition;
			log.debug(hql);
			Query query = sessionFactory.getCurrentSession().createQuery(hql);
			if (params != null) {
				for (String key : params.keySet()) {
					query.setParameter(key, params.get(key));
				}
			}
			Long count = (Long) query.uniqueResult();

			log.debug("query successful");
			if (count == null)
				return 0;

			long pageCount= count % pageSize == 0 ? (count / pageSize) : (count
					/ pageSize + 1);
			return (int)pageCount;
		} catch (RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}
	}
	
	public List<T> getPageByHql(int pageNumber, int pageSize, String hql,
			Map<String, Object> params) {
		try {
			int first = pageSize * (pageNumber - 1);

			
			if(params==null){
				params=new HashMap<String,Object>();
			}
			
			Query query = sessionFactory.getCurrentSession().createQuery(hql);
			for (String key : params.keySet()) {
				query.setParameter(key, params.get(key));
			}
			query.setFirstResult(first);
			query.setMaxResults(pageSize);

			log.debug("query successful");
			return (List<T>) query.list();

		} catch (RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}
	}
	
	public int getPageCountByHql(int pageSize, String hql,
			Map<String, Object> params) {
		try {
		
			Long count = (Long) this.getUniqueResultByHql(hql, params);

			log.debug("query successful");
			if (count == null)
				return 0;

			Long pageCount= count % pageSize == 0 ? (count / pageSize) : (count
					/ pageSize + 1);
			return  pageCount.intValue();
		} catch (RuntimeException re) {
			log.error("merge failed", re);
			throw re;
		}
	}

	public Object getUniqueResultByHql(String queryHql,Map<String,Object> params){
		Query query=sessionFactory.getCurrentSession().createQuery(queryHql.toString());
		
		if(params!=null){
			for(String key:params.keySet()){
				query.setParameter(key, params.get(key));
			}
		}
		Object result=query.uniqueResult();
		return result;		
	}
	
	public List getResultByHql(String queryHql,Map<String,Object> params){
		Query query=sessionFactory.getCurrentSession().createQuery(queryHql.toString());
		
		if(params!=null){
			for(String key:params.keySet()){
				query.setParameter(key, params.get(key));
			}
		}
		List result=query.list();
		return result;		
	}
	
	public PageResult<T> getPageResult(int pageNumber,int pageSize,String condition,Map<String,Object> params){
		if(condition==null){
			condition="";
		}
		String className=StringUtils.getClassName(this.persistentClass);
		//String conditionHql=condition+" order by "+className+".id desc";
		List<T> results=this.getPage(pageNumber, pageSize,condition ,params);
		int pageCount=this.getPageCount(pageSize, condition, params);
		PageResult<T> result=new PageResult<T>(results,pageNumber,pageCount);
		return result;
	}
}
